<?php

declare(strict_types=1);

namespace Vipkwd\SDK\OAuth\Dependents;

class Utils
{
    private static $logDebug = !true;
    /**
     * 检测数组是否为索引数组(且从0序开始)
     *
     *
     * @param mixed $value
     */
    static function isIndexList($value): bool
    {
        return is_array($value) && (PHP_VERSION_ID < 80100
            ? !$value || array_keys($value) === range(0, count($value) - 1)
            : array_is_list($value)
        );
    }
    /**
     * 字符XSS过滤
     *
     * @param string|array $str 待检字符 或 索引数组
     * @param boolean $dpi <false> 除常规过滤外，是否深度(额外使用正则)过滤。默认false仅常规过滤
     * @return string|array
     */
    static function removeXss($str, bool $dpi = false, bool $keepHtmlTag = false)
    {
        if (!is_array($str)) {
            $str = trim($str);
            $keepHtmlTag === false && $str = strip_tags($str);
            $str = htmlspecialchars($str);
            if ($dpi === true) {
                $str = str_replace(array('"', "\\", "'", "/", "..", "../", "./", "//"), '', $str);
                $no = '/%0[0-8bcef]/';
                $str = preg_replace($no, '', $str);

                // 移除 百分号后两位字符
                $no = '/%1[0-9a-f]/';
                $str = preg_replace($no, '', $str);

                // 移除W3C的标准下，XML文件无法识别的字符
                $no = '/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]+/S';
                $str = preg_replace($no, '', $str);
            }
            return $str;
        }
        foreach ($str as $k => $v) {
            $str[$k] = self::removeXss($v, $dpi);
        }
        return $str;
    }

    /**
     * 数据解码(xss)
     * 
     * @param mixed $data
     * @param bool $rxss <true> 是否转义为实体符
     * 
     * @return mixed
     * 
     */
    static function encode($data, $rxss = true)
    {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                $data[$k] = self::encode($v, $rxss);
            }
            return $data;
        } else {
            $type = strtolower(gettype($data));
            switch ($type) {
                case "boolean":
                case "object":
                case "integer":
                case "double":
                case "null":
                    break;
                case "string":
                    $data = trim($data);
                    $firstChar = substr($data, 0, 1);
                    $lastChar = substr($data, -1);
                    if (($firstChar == '{' && $lastChar == '}') || ($firstChar == '[' && $lastChar == ']')) {
                        return self::encode(json_decode($data, true), $rxss);
                    } else {
                        $data = urldecode($data);
                        $data = trim($rxss ? self::removeXss($data) : $data);
                    }
                    break;
                default:
                    $data = Null;
                    break;
            }
            return $data;
        }
    }

    /**
     * 记录日志
     */
    static function log($data, string $event = 'info', string $logFile = null)
    {
        if (!self::$logDebug)
            return;

        if ($logFile) {
            $logFile = realpath($logFile);
            if (!is_dir(dirname($logFile))) {
                mkdir(dirname($logFile), 0755, true);
            }
        } else {
            $logFile = (realpath(__DIR__ . '/../../../../../')) . '/vk-oauth.log';
        }

        switch (strtolower(gettype($data))) {
            case "boolean":
            case "bool":
                $data = 'Bool::' . ($data === true ? 'true' : 'false');
                break;
            case "null":
                $data = 'Null::null';
                break;
            case "object":
                $data = get_object_vars($data);
                $data = "Object::" . json_encode($data);
                break;
            case "array":
                if (isset($data['expires_time']))
                    $data['expires_time_'] = date('Y-m-d H:i:s', $data['expires_time']);
                if (isset($data['refresh_token_time']))
                    $data['refresh_token_time_'] = date('Y-m-d H:i:s', $data['refresh_token_time']);
                $data = json_encode($data);
                break;
        }
        $backTrace = debug_backtrace();

        $traceList = [];
        foreach ($backTrace as $k => $trace) {
            $traceStr = PHP_EOL;
            $traceStr .= sprintf("    %s(%d) ", $trace['file'], $trace['line']);
            $traceStr .= sprintf(" %s%s%s(...", $trace['class'], $trace['type'], $trace['function']);

            $args = json_encode($trace['args']); //剔除对象嵌套
            $args = json_decode($args, true);
            $args = var_export($args, true);//导出数组结构
            $args = explode("\n", $args);

            foreach ($args as $k => $v) {
                if ($k > 0)
                    $args[$k] = '       ' . $v;
            }
            $args = implode("\n", $args);
            $traceStr .= sprintf("%s)%s\r\n", $args, PHP_EOL);

            $traceList[] = $traceStr;
        }
        $traceStr = '';
        $length = count($traceList) - 1;
        for ($i = 0; $i <= $length; $i++) {
            $traceStr .= ("    " . str_pad(strval($length - $i + 1), 2, ' ', STR_PAD_RIGHT) . ': ' . ltrim($traceList[$i]));
        }

        $data = sprintf("%s[%s][%s] %s\r\n", PHP_EOL, date('Y-m-d H:i:s'), $event, $data);
        file_put_contents($logFile, $data, FILE_APPEND);
        file_put_contents($logFile, $traceStr, FILE_APPEND);
        unset($data, $logFile, $traceStr, $traceList, $backTrace);
    }

    static function cryptRC4(string $str, string $opt = 'E', string $key = '')
    {
        $key = md5($key);
        $key_length = strlen($key);
        $str = $opt == 'D' ? base64_decode($str) : substr(md5($str . $key), 0, 8) . $str;
        $str_length = strlen($str);
        $rndkey = $box = array();
        $rest = '';
        for ($i = 0; $i <= 255; $i++) {
            $rndkey[$i] = ord($key[$i % $key_length]);
            $box[$i] = $i;
        }
        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }
        for ($a = $j = $i = 0; $i < $str_length; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $rest .= chr(ord($str[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
        if ($opt == 'D') {
            if (substr($rest, 0, 8) == substr(md5(substr($rest, 8) . $key), 0, 8)) {
                return substr($rest, 8);
            } else {
                return '';
            }
        } else {
            return str_replace('=', '', base64_encode($rest));
        }
    }
    static function encryptSha256(string $input, string $salt)
    {
        return hash_hmac('sha256', $input, $salt, true);
    }
    static function base64Encode($input)
    {
        if (is_array($input) || is_object($input)) {
            $input = json_encode((array) $input);
        }
        return self::urlSafeB64Encode($input);
    }

    static function base64Decode($input, bool $toArray = true)
    {
        $input = self::urlSafeB64Decode($input);
        $input = base64_encode($input);
        if ($toArray && substr($input, 0, 1) == '{' && substr($input, -1) == '}') {
            $input = json_decode($input, true);
        }
        return $input;
    }

    /**
     * @param string $a
     * @param string $b
     * @return bool
     */
    static function hash_equals($a, $b)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($a, $b);
        }
        $diff = strlen($a) ^ strlen($b);
        for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
            $diff |= ord($a[$i]) ^ ord($b[$i]);
        }

        return $diff === 0;
    }

        /**
     * @param string $data
     * @return string
     */
    static function urlSafeB64Encode($data)
    {
        $b64 = base64_encode($data);
        $b64 = str_replace(array('+', '/', "\r", "\n", '='),
                array('-', '_'),
                $b64);

        return $b64;
    }

    /**
     * @param string $b64
     * @return mixed|string
     */
    static function urlSafeB64Decode($b64)
    {
        $b64 = str_replace(array('-', '_'),
                array('+', '/'),
                $b64);

        return base64_decode($b64);
    }
}